﻿using System;
using System.IO;
using System.Web;
using EPiServer;
using EPiServer.Core;
using EPiServer.DataAccess;
using EPiServer.Framework;
using EPiServer.Security;
using TemplateFoundation.Core;
using TemplateFoundation.Exceptions;
using TemplateFoundation.PageTypes;

namespace TemplateFoundation.Configuration
{
    /// <summary>
    /// Singleton class for accessing site settings
    /// </summary>
    public class Settings
    {
        private static Settings _instance;

        /// <summary>
        /// Initializes a new instance of the <see cref="Settings"/> class.
        /// </summary>
        private Settings()
        {

        }

        /// <summary>
        /// Provides access to site settings
        /// </summary>
        public static Settings Instance
        {
            get
            {
                if(_instance==null)
                    _instance = new Settings();

                return _instance;
            }
            set
            {
                _instance = value;
            }
        }

        /// <summary>
        /// Gets or sets a reference to the container for news pages
        /// </summary>
        public PageReference NewsContainer
        {
            get
            {
                return StartPage==null ? PageReference.EmptyReference : StartPage.NewsContainer;
            }
            set
            {
                var page = GetWritableStartPage();
                page.NewsContainer = value;
                SaveStartPageAfterModifyingSettings(page);
            }
        }

        /// <summary>
        /// Gets or sets a reference to the container for contact pages
        /// </summary>
        public PageReference ContactsContainer
        {
            get
            {
                return StartPage==null ? PageReference.EmptyReference : StartPage.ContactsContainer;
            }
            set
            {
                var page = GetWritableStartPage();
                page.ContactsContainer = value;
                SaveStartPageAfterModifyingSettings(page);
            }
        }

        /// <summary>
        /// Gets or sets the ID of the page type used for news date folders
        /// </summary>
        public int? DateFolderPageTypeId
        {
            get
            {
                try
                {
                    return StartPage==null ? null : StartPage.DateFolderPageTypeId;
                }
                catch (NullReferenceException)
                {
                    throw new TemplateFoundationException("The 'NewsDateFolderPageTypeId' property hasn't been set, please check its value by editing site settings on the start page");
                }
            }
            set
            {
                var page = GetWritableStartPage();
                page.DateFolderPageTypeId = value;
                SaveStartPageAfterModifyingSettings(page);
            }
        }

        /// <summary>
        /// Gets or sets a reference to the tags container page
        /// </summary>
        public PageReference TagsContainer
        {
            get
            {
                // If the root page is set as the start page we cannot retrieve any settings from it
                if(PageReference.StartPage==PageReference.RootPage || StartPage==null)
                {
                    return PageReference.EmptyReference;
                }

                return StartPage.TagsContainer;
            }
            set
            {
                var page = GetWritableStartPage();
                page.TagsContainer = value;
                SaveStartPageAfterModifyingSettings(page);
            }
        }

        /// <summary>
        /// Gets or sets the ID of the page type used for tag pages
        /// </summary>
        public int? TagPageTypeId
        {
            get
            {
                try
                {
                    // Return null if no start page reference is available of if it's set to the root page
                    if (PageReference.StartPage==PageReference.RootPage || StartPage==null)
                    {
                        return null;
                    }

                    return StartPage.TagPageTypeId;
                }
                catch (NullReferenceException)
                {
                    throw new TemplateFoundationException("The 'TagPageTypeId' property hasn't been set, please check its value by editing site settings on the start page");
                }
            }
            set
            {
                var page = GetWritableStartPage();
                page.TagPageTypeId = value;
                SaveStartPageAfterModifyingSettings(page);
            }
        }

        /// <summary>
        /// Gets or sets the page type to use for news articles and blog posts
        /// </summary>
        /// <remarks>Also used when posting posts through MetaWeblog</remarks>
        public int? ArticlePageTypeId
        {
            get
            {
                try
                {
                    return StartPage != null ? StartPage.ArticlePageTypeId : null;
                }
                catch (NullReferenceException)
                {
                    throw new TemplateFoundationException("The 'ArticlePageTypeId' property hasn't been set, please check its value by editing site settings on the start page");
                }
            }
            set
            {
                var page = GetWritableStartPage();
                page.ArticlePageTypeId = value;
                SaveStartPageAfterModifyingSettings(page);
            }
        }

        /// <summary>
        /// Gets or sets the page type to use for standard pages
        /// </summary>
        /// <remarks>Also used when posting pages through MetaWeblog</remarks>
        public int? StandardPageTypeId
        {
            get
            {
                return StartPage != null ? StartPage.StandardPageTypeId : null;
            }
            set
            {
                var page = GetWritableStartPage();
                page.StandardPageTypeId = value;
                SaveStartPageAfterModifyingSettings(page);
            }
        }

        /// <summary>
        /// Gets or sets if date folder sorting should be disabled for the news container
        /// </summary>
        public bool DisableDateFolderSorting
        {
            get
            {
                return StartPage!=null ? StartPage.DisableDateFolderSorting : false;
            }
            set
            {
                var page = GetWritableStartPage();
                page.DisableDateFolderSorting = value;
                SaveStartPageAfterModifyingSettings(page);
            }
        }

        /// <summary>
        /// Gets the virtual path to the main JavaScript file
        /// </summary>
        /// <param name="minified">Set to true to get path to minified JavaScript file</param>
        /// <returns></returns>
        public string GetMainJavaScriptPath(bool minified)
        {
            return GetJavaScriptPath("main", minified);
        }

        /// <summary>
        /// Gets or sets whether default JavaScript links should be rendered
        /// </summary>
        /// <remarks>The default JavaScript links point to /Templates/JavaScript/main.js and main.min.js (depending on whether or not in debug mode)</remarks>
        public bool DisableJavaScriptLinks
        {
            get
            {
                return StartPage!=null ? StartPage.DisableJavaScriptLinks : false;
            }
            set
            {
                var page = GetWritableStartPage();
                page.DisableJavaScriptLinks = value;
                SaveStartPageAfterModifyingSettings(page);
            }
        }

        /// <summary>
        /// Gets the virtual path to the main stylesheet file
        /// </summary>
        /// <param name="minified">Set to true to get path to minified stylesheet file</param>
        /// <returns></returns>
        public string GetMainStylesheetPath(bool minified)
        {
            return GetStylesheetPath("main", minified);
        }

        /// <summary>
        /// Gets the virtual path to a stylesheet file
        /// </summary>
        /// <param name="filename">The name of the stylesheet file, extension can be excluded</param>
        /// <param name="minified">Set to true to get path to minified stylesheet file</param>
        /// <returns></returns>
        public string GetStylesheetPath(string filename, bool minified)
        {
            var filenameWithoutExtension = filename;

            if(filenameWithoutExtension.Contains("."))
            {
                filenameWithoutExtension = filenameWithoutExtension.Substring(0, filenameWithoutExtension.IndexOf("."));
            }

            return string.Concat(Instance.StaticFilesPath,
                                 "css/",
                                 string.Concat(filenameWithoutExtension, minified ? ".min.css" : ".css"));
        }

        /// <summary>
        /// Gets the virtual path to a JavaScript file
        /// </summary>
        /// <param name="filename">The name of the JavaScript file, extension can be excluded</param>
        /// <param name="minified">Set to true to get path to minified stylesheet file</param>
        /// <returns></returns>
        public string GetJavaScriptPath(string filename, bool minified)
        {
            var filenameWithoutExtension = filename;

            if (filenameWithoutExtension.Contains("."))
            {
                filenameWithoutExtension = filenameWithoutExtension.Substring(0, filenameWithoutExtension.IndexOf("."));
            }

            return string.Concat(Instance.StaticFilesPath,
                                 "js/",
                                 string.Concat(filenameWithoutExtension, minified ? ".min.js" : ".js"));
        }

        /// <summary>
        /// Gets or sets whether default stylesheet links should be rendered
        /// </summary>
        /// <remarks>The default stylesheet links point to /Templates/Stylesheets/main.css</remarks>
        public bool DisableStylesheetLinks
        {
            get
            {
                try
                {
                    return StartPage != null ? StartPage.DisableStylesheetLinks : false;
                }
                catch (NullReferenceException)
                {
                    throw new TemplateFoundationException("Unable to retrieve the 'DisableStylesheetLinks' setting");
                }
            }
            set
            {
                var page = GetWritableStartPage();
                page.DisableStylesheetLinks = value;
                SaveStartPageAfterModifyingSettings(page);
            }
        }

        /// <summary>
        /// Gets or sets the page type to use for person pages, for example contact persons
        /// </summary>
        public int? PersonPageTypeId
        {
            get
            {
                return StartPage!=null ? StartPage.PersonPageTypeId : null;
            }
            set
            {
                var page = GetWritableStartPage();
                page.PersonPageTypeId = value;
                SaveStartPageAfterModifyingSettings(page);
            }
        }

        /// <summary>
        /// Gets or sets the virtual path to the static files folder used for scripts, stylesheets and graphics
        /// </summary>
        /// <remarks>Defaults to '/Templates/'</remarks>
        public string StaticFilesPath
        {
            get
            {
                var path = StartPage==null ? null : StartPage.StaticFilesPath;

                if (string.IsNullOrEmpty(path))
                {
                    path = "/Templates/";
                }

                path = VirtualPathUtility.AppendTrailingSlash(path);

                if (!path.StartsWith("/"))
                {
                    path = string.Concat("/", path);
                }

                return path;
            }
            set
            {
                var page = GetWritableStartPage();
                page.StaticFilesPath = value;
                SaveStartPageAfterModifyingSettings(page);
            }
        }

        /// <summary>
        /// Gets contact details for the company, commonly used for page footers and contact pages
        /// </summary>
        public CompanyContact ContactInformation
        {
            get
            {
                if (StartPage==null)
                {
                    if (PageReference.StartPage.ID==PageReference.RootPage.ID)
                    {
                        throw new TemplateFoundationException("Start page ID has not been specified in configuration, please set the 'pageStartId' attribute in episerver.config");
                    }

                    throw new TemplateFoundationException("Unable to retrieve start page");
                }

                var s = StartPage;

                return new CompanyContact
                           {
                               Phone = s.CompanyPhoneNumber,
                               Email = s.CompanyEmail,
                               Address = s.CompanyStreetAddress,
                               ZipCode = s.CompanyZipCode,
                               City = s.CompanyCity,
                               CompanyName = s.CompanyName
                           };
            }
            set
            {
                var page = GetWritableStartPage();

                page.CompanyPhoneNumber = value.Phone;
                page.CompanyEmail = value.Email;
                page.CompanyStreetAddress = value.Address;
                page.CompanyZipCode = value.ZipCode;
                page.CompanyCity = value.City;
                page.CompanyName = value.CompanyName;

                SaveStartPageAfterModifyingSettings(page);
            }
        }

        /// <summary>
        /// Gets the start page
        /// </summary>
        internal StartPageBase StartPage
        {
            get
            {
                try
                {
                    return PageFactory.Instance.GetStartPage();
                }
                catch (EPiServerException)
                {
                    // HACK This will slow down ETF startup if the tag property for the start page has been set
                    // For cases where the current thread might already be reading the start page causing risk of a thread deadlock
                    // (for example when tag settings are read from the start page while the start page is being tagged)
                    var version = DataFactory.Instance.LoadPublishedVersion(PageReference.StartPage);

                    if (version!=null)
                    {
                        return version.ID.GetPage() as StartPageBase;
                    }

                    return null;
                }
            }
        }

        internal static StartPageBase GetWritableStartPage()
        {
            if (PageReference.StartPage==PageReference.RootPage)
            {
                throw new TemplateFoundationException("There is no start page to hold site settings, or it hasn't been properly specified in episerver.config");
            }

            return (StartPageBase)PageFactory.Instance.GetStartPage().CreateWritableClone();
        }

        internal static void SaveStartPageAfterModifyingSettings(StartPageBase page)
        {
            if (!page.PendingPublish)
            {
                page.Save(SaveAction.Publish | SaveAction.ForceCurrentVersion, AccessLevel.NoAccess);
            }
            else
            {
                page.Save(SaveAction.Save, AccessLevel.NoAccess);
            }
        }

        /// <summary>
        /// Represents contact information for the site owner company
        /// </summary>
        public struct CompanyContact
        {
            /// <summary>
            /// Company name
            /// </summary>
            public string CompanyName;
            /// <summary>
            /// Phone number
            /// </summary>
            public string Phone;
            /// <summary>
            /// E-mail address
            /// </summary>
            public string Email;
            /// <summary>
            /// Street address
            /// </summary>
            public string Address;
            /// <summary>
            /// Zip code
            /// </summary>
            public string ZipCode;
            /// <summary>
            /// City
            /// </summary>
            public string City;
        }
    }
}
